#include "../sleipner_config.h"
#include "ogl_surface.h"

#ifdef _OPENGLES2
#include "ogl_graphics.h"
#endif

#include "ui/ui_manager.h"
#include "graphics/graphics.h"
#include "resman/resman.h"



#include "util/log.h"				// log


extern "C"
{
    
#ifdef _SDL2

    #include "SDL2/include/SDL.h"
	#ifdef WIN32
        #include "glee/glee.h"
        //#include "SDL-1.2.13/include/SDL_opengl.h"
    #else
#ifdef _OPENGLES2
        #include "SDL2/include/SDL_opengles2.h"
#else
	#include "SDL2/include/SDL_opengles.h"
#endif
    #endif

#else


    #include "SDL-1.2.13/include/SDL.h"
    #ifndef _PLATFORM_IOS
        #include "glee/glee.h"
        //#include "SDL-1.2.13/include/SDL_opengl.h"
    #else
        #include "SDL-1.2.13/include/SDL_opengles.h"
    #endif

#endif

#include "libjpeg/jpeglib.h"

}
#include "libpng/png.h"




static void	setup_rw_source(jpeg_decompress_struct* cinfo, vfs::LoadedBlock* instream);

res::ResourceFile*	OglSurface::CreateImage()
{
	// construct
	return new OglSurface();
}

OglSurface::OglSurface() :Surface(),
m_Texture(0),
m_ColorChannel(3)
{
}

OglSurface::~OglSurface()
{	
	glDeleteTextures(1,&m_Texture);
}


void	OglSurface::RegisterFormats()
{
	static bool AlreadyInitialized = false;
	if (AlreadyInitialized) return;
	AlreadyInitialized = true;
    
	res::ResMan& RM = res::ResMan::Instance();
	// register this resource constuctor with the following formats.
	RM.AddResourceConstructor(CreateImage, "jpg");
	RM.AddResourceConstructor(CreateImage, "png");
    
	_LOG(MSG_INIT, "File formats registered for surface");
}

void	OglSurface::InitTexture()
{
    
	while(glGetError() != GL_NO_ERROR);		// clear previous errors
    
#ifdef _OPENGLES2
#else
    glEnable(GL_TEXTURE_2D);
#endif
    
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_SRC_COLOR);
    
	// allocate a texture name
	glGenTextures( 1, &m_Texture );
	// select our current texture
	glBindTexture( GL_TEXTURE_2D, m_Texture );
	
	int W=0,H=0;
	unsigned char* ImageData;
	
	const char*     File    = m_Data.FileName.c_str();
	size_t          SL      = strlen(File);
	char	        Ext[]   = {
        static_cast<char>(tolower(File[SL-3])),
		static_cast<char>(tolower(File[SL-2])),
		static_cast<char>(tolower(File[SL-1])),
        '\0'};
	
    if (File[SL-4] != '.')  // all internal file formats are 3 lettered extensions
    {
        _LOG(MSG_WARNING, "Unable to find the file-type of image file " << m_Data.FileName);
		m_Texture = 0;
        return;
    }
    
	if (strcmp(Ext, "jpg")==0)
	{
		JpegLoad(m_Data, &ImageData, W, H , m_ColorChannel);		
	}
	else if (strcmp(Ext, "png")==0) 
	{
		if(PngLoad(m_Data ,  &ImageData , W, H, m_ColorChannel) != 1)
		{
			m_Texture = 0;
			return;
		}
	}

    
    
    
	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	//glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); 
    //glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);	
	
	//if(IsPowerOfTwo(W) && IsPowerOfTwo(H))
	//{
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	//}
	//else
	//{
	//	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	//	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );				
	//}
	
    //#ifndef _PLATFORM_IOS
	
	if( m_ColorChannel == 3)
	{
		//if(IsPowerOfTwo(W) && IsPowerOfTwo(H))
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, W, H, 0, GL_RGB, GL_UNSIGNED_BYTE, ImageData);
		//else        
		//	gluBuild2DMipmaps(GL_TEXTURE_2D, m_ColorChannel, W, H, GL_RGB, GL_UNSIGNED_BYTE, ImageData);		
	}
	else if(m_ColorChannel == 4)
	{
        
        
        
		//if(IsPowerOfTwo(W) && IsPowerOfTwo(H))
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, ImageData);
        
        
		//else
		//	gluBuild2DMipmaps(GL_TEXTURE_2D, m_ColorChannel, W, H, GL_RGBA, GL_UNSIGNED_BYTE, ImageData);
        
	}
    //#else
    
    
    
    //   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, ImageData);
    
    
    //#endif	
#ifdef _OPENGLES2
	glGenerateMipmap(GL_TEXTURE_2D);
#endif
	
	GLenum   ErrorCode = glGetError();
	if (ErrorCode == GL_NO_ERROR)
    {
		m_State = RS_READY;
		_LOG(MSG_LOAD, "Loaded texture " << m_Data.FileName);
		ui::UIManager::Instance().UpdateLoadCB();
    } else
    {
		m_State = RS_FAILED;
		_LOG(MSG_LOAD, "Failed to loaded texture " << m_Data.FileName);
    }	
	
	m_Width = W;
	m_Height = H;
	//m_Format = Info.Format;
	
	//we are done here, release the memory
	free(ImageData);
	m_Data.Reset();
    
    glBindTexture( GL_TEXTURE_2D, 0 );
}


/// remove all data from RAM, but keep track of origin so that it can be reaquired
void			OglSurface::ReleaseResource()
{
	glDeleteTextures( 1, &m_Texture );
	Surface::ReleaseResource();
}

bool			OglSurface::SetTextureStage(unsigned int _TexStage)
{
	if (m_State == RS_RELEASED)
	{
		// we've released it. See if we can re-load it now?
		InitializeResource();
	} 
    
	if(m_Texture == 0)
		return false;
    
	if (m_State == RS_READY)
	{
		
#ifdef _OPENGLES2
		while(glGetError() != GL_NO_ERROR);

		glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, m_Texture);		
		const GLuint TEXTURE_LOC = glGetUniformLocation( OglGraphics::m_EGLProgram ,"u_texture");;
        glUniform1i(TEXTURE_LOC, 0);
		
		GLenum   ErrorCode = glGetError();
		if (ErrorCode != GL_NO_ERROR)
		{			
			char ErrorString[512];
			sprintf( ErrorString, "Fail To Set Texture! Error: %d TextureLoc:%d", ErrorCode, TEXTURE_LOC); 
			_LOG(MSG_FATAL, ErrorString);
		}			

#else
        glBindTexture( GL_TEXTURE_2D, m_Texture );
		glEnable( GL_TEXTURE_2D );
#endif
        
		return true;
	}
	return false;
}

float*			OglSurface::GetColors()
{
	// create array
	float*	ColorA = new float[m_Height*m_Width*4];
	return ColorA;
}

COLOR*		OglSurface::GetPixels()
{	
	// create array
	COLOR*	ColorA = new COLOR[m_Height*m_Width];	
	return ColorA;
}


AlphaMask*	OglSurface::GenerateMask()
{
	if(m_ColorChannel == 3)
		return NULL;
    
	if(!SetTextureStage(0))
		return NULL;
    
	AlphaMask* pMask = new AlphaMask();   // caller is responsible to release memory
	pMask->Height = m_Height;
	pMask->Width = m_Width;
	pMask->AlphaVal = new unsigned char [m_Height*m_Width*4];
#ifdef PLATFORM_WIN32
	ZeroMemory(pMask->AlphaVal,((m_Width * m_Height * 4)* sizeof(unsigned char)));	// Clear Storage Memory
#else
	for(int i =0; i < (int)(m_Width * m_Height * 4); i++)
		pMask->AlphaVal[i] = 0;
#endif	
    
	while(glGetError() != GL_NO_ERROR);
    
#ifdef _PLATFORM_WIN32
	glGetTexImage(GL_TEXTURE_2D ,0,GL_RGBA,GL_UNSIGNED_BYTE ,pMask->AlphaVal);	
	glBindTexture( GL_TEXTURE_2D, 0 );
	glDisable( GL_TEXTURE_2D );
#else
	
	//glReadBuffer( GL_BACK );		// chit: not availible in opengles
    glReadPixels(	0, // x
                 0, // y
                 m_Width, 
                 m_Height, 
                 GL_RGBA, 
                 GL_UNSIGNED_BYTE, 
                 pMask->AlphaVal);
#endif
    
	GLenum  ErrCode = glGetError();
	if (ErrCode != GL_NO_ERROR)    			
		bool testbreak = true;
    
	/*
     int index = 0;
     for(int i =0; i < (int)(m_Height); i++)
     {
     for(int j =0; j < (int)(m_Width); j++)
     {
     float Temp = pMask->AlphaVal[index]; 
     _LOG(MSG_INIT,Temp  );
     
     }
     }
     */
    
	return pMask;	
    
}

void ReadDataFromLoadedBlock(png_structp png_ptr, png_bytep outBytes,
                             png_size_t byteCountToRead);

int		OglSurface::PngLoad(vfs::LoadedBlock& LoadedData ,unsigned char**  image_data_ptr
							, int &Width, int &Height,int &Channel)
{
    
	
    png_structp   png_ptr;        /* internally used by libpng */
    png_infop     info_ptr;       /* user requested transforms */
    
    char         *image_data;      /* raw png image data */
    char         sig[8];           /* PNG signature array */
    
    
    int           bit_depth;
    int           color_type;
    
    unsigned long width;            /* PNG image width in pixels */
    unsigned long height;           /* PNG image height in pixels */
    unsigned int rowbytes;         /* raw bytes at row n in image */
    
    image_data = NULL;
    int i;
    png_bytepp row_pointers = NULL;
    
    
	if(LoadedData.Size > 8)
    {
	    LoadedData.ReadData(sig,8);
		if (!png_check_sig((unsigned char *) sig, 8)) 
		{
			return 0;
		}
    }
    
    /* 
     * Set up the PNG structs 
     */
    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr) {
        return 4;    /* out of memory */
    }
    
    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
        png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL);
        return 4;    /* out of memory */
    }
    
    /*
     * block to handle libpng errors, 
     * then check whether the PNG file had a bKGD chunk
     */
    if (setjmp(png_jmpbuf(png_ptr))) {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        return 0;
    }
    
    // pass our own function to feed data
    png_set_read_fn(png_ptr,&LoadedData,ReadDataFromLoadedBlock);
    
    /*
     * lets libpng know that we already checked the 8 
     * signature bytes, so it should not expect to find 
     * them at the current file pointer location
     */
    png_set_sig_bytes(png_ptr, 8);
    
    /* read all the info up to the image data  */
    png_read_info(png_ptr, info_ptr);
    
    png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, 
                 &color_type, NULL, NULL, NULL);
    
    // convert appropriate formats!
    if (color_type == PNG_COLOR_TYPE_PALETTE)
		png_set_palette_to_rgb(png_ptr);
    
    if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
		png_set_gray_1_2_4_to_8(png_ptr);
    
	if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
		png_set_gray_to_rgb(png_ptr);
    
	if (bit_depth == 16)
		png_set_strip_16(png_ptr);
    
    /* Update the png info struct.*/
    png_read_update_info(png_ptr, info_ptr);
    
    Channel = info_ptr->channels;
    /*   if(!IsPowerOfTwo((int)png_ptr->width))
     {
     GetPowerOfTwo((int&)png_ptr->width);
     }  */  
    
    /* Rowsize in bytes. */
    rowbytes = png_get_rowbytes(png_ptr, info_ptr);
    
    /* Allocate the image_data buffer. */
    if ((  image_data = ( char *) malloc(rowbytes * height))==NULL) {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        return 4;
    }
	
	
    if ((row_pointers = (png_bytepp)malloc(height*sizeof(png_bytep))) == NULL) {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        free(image_data);
        image_data = NULL;
        return 4;
    }
    
    // For Normal V
    /* set the individual row_pointers to point at the correct offsets */
    for (i = 0;  i <(int) height;  ++i)
        row_pointers[i] = (png_byte*) image_data + i*rowbytes;
    
    // For upside down
    //  for (i = 0;  i <(int) height;  ++i)
    //    row_pointers[height - i - 1] = (png_byte*) image_data + i*rowbytes;
    
    /* now we can go ahead and just read the whole image */
    png_read_image(png_ptr, row_pointers);
    
    /* and we're done!  (png_read_end() can be omitted if no processing of
     * post-IDAT text/time/etc. is desired) */
    
	Width = width;
    Height = height;
	
    /* Clean up. */
    free(row_pointers);
    
    /* Clean up. */
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
	
    *image_data_ptr = (unsigned char*)image_data;
    
    return 1;
}
void ReadDataFromLoadedBlock(png_structp png_ptr, png_bytep outBytes,
                             png_size_t byteCountToRead)
{
    if(png_ptr->io_ptr == NULL)
        return;   // add custom error handling here
    
	vfs::LoadedBlock& LoadedData = *(vfs::LoadedBlock*)png_ptr->io_ptr;
	LoadedData.ReadData( outBytes , (unsigned long)byteCountToRead);
    
}



int		OglSurface::JpegLoad(vfs::LoadedBlock& LoadedData ,unsigned char** image_data_ptr ,
							 int &Width, int &Height ,int &Channel)
{
	/* these are standard libjpeg structures for reading(decompression) */
		struct jpeg_decompress_struct cinfo;
		struct jpeg_error_mgr jerr;
		/* libjpeg data structure for storing one row, that is, scanline of an image */
		JSAMPROW row_pointer[1];

		int i = 0;

		/* here we set up the standard libjpeg error handler */
		cinfo.err = jpeg_std_error( &jerr );
		/* setup decompression process and source, then read JPEG header */
		jpeg_create_decompress( &cinfo );
		setup_rw_source(&cinfo,&LoadedData);

		/* reading the image header which contains image information */
		jpeg_read_header( &cinfo, TRUE );

		Width  = cinfo.image_width;
		Height = cinfo.image_height;

		/* Uncomment the following to output image information, if needed. */
		/*--
		printf( "JPEG File Information: \n" );
		printf( "Color components per pixel: %d.\n", cinfo.num_components );
		printf( "Color space: %d.\n", cinfo.jpeg_color_space );
		--*/
		/* Start decompression jpeg here */
		jpeg_start_decompress( &cinfo );

		/* allocate memory to hold the uncompressed image */
		unsigned char* raw_image = (unsigned char*)malloc( cinfo.output_width*cinfo.output_height*cinfo.num_components );
		/* now actually read the jpeg into the raw buffer */
		row_pointer[0] = (unsigned char *)malloc( cinfo.output_width*cinfo.num_components );
		/* read one scan line at a time */

		int h = 0 ,w = 0;
		int index = 0;
		w = cinfo.image_width*cinfo.num_components;
		while( cinfo.output_scanline < cinfo.image_height )
		{
			jpeg_read_scanlines( &cinfo, row_pointer, 1 );
			for( i=0; i<w;i++)
			{
				//  we need to flip
				//	coz opengl texture format is upside down
				// index = ((cinfo.image_height-h-1)*w) + i ;
				 raw_image[index++] = row_pointer[0][i];
			}

			h++;
		}
		/* wrap up decompression, destroy objects, free pointers and close open files */
		jpeg_finish_decompress( &cinfo );
		jpeg_destroy_decompress( &cinfo );

		free( row_pointer[0] );
		/* yup, we succeeded! */



		//if(IsPowerOfTwo(Width) && IsPowerOfTwo(Height))
		//{
			Channel = cinfo.num_components;
			*image_data_ptr = raw_image;
			return 1;
		//}

		/*
		// not power of two?
		// we need to pad with alpha

		int ExpandedWidth = NextPowerof2(Width);
		int ExpandedHeight = NextPowerof2(Height);
		unsigned char* Expanded_Data = (unsigned char*)malloc( ExpandedWidth*ExpandedHeight* 4 );

		int Expandedindex = 0;
		int ImageIndex    = 0;
		for(int j= 0 ; j < ExpandedHeight  ;j++)
		 {
			for(int i=0; i < ExpandedWidth; i++)
			{
				if( i == 0)
				{
					int testing = 0;
				}

				if( j < Height && i < Width)
				{
					Expanded_Data[Expandedindex++]  = 	raw_image[ImageIndex++];   // R
					Expanded_Data[Expandedindex++]  = 	raw_image[ImageIndex++];   // G
					Expanded_Data[Expandedindex++]  = 	raw_image[ImageIndex++];   // B
					Expanded_Data[Expandedindex++]  = 	254;                       // A
				}
				else
				{
					Expanded_Data[Expandedindex++]  = 	255;
					Expanded_Data[Expandedindex++]  = 	0;
					Expanded_Data[Expandedindex++]  = 	0;
					Expanded_Data[Expandedindex++]  = 	0;
				}

			}
		 }

		delete[] raw_image;
		*image_data_ptr = Expanded_Data;
		Width   = ExpandedWidth;
		Height  = ExpandedHeight;
		Channel = 4;


		return 1;
		*/
}



bool	OglSurface::IsPowerOfTwo(int Number)
{
	int Result = 0;
	Result = Number / 2;
    
	if ( Result < 1)
		return false;
    
	do
	{
		if(Number %2 != 0)
			return false;
		Number = Number/2;
	}
	while(Number != 1);
    
	return true;
    
}

inline int OglSurface::NextPowerof2 (int Num )
{
	int rval=1;
	// rval<<=1 Is A Prettier Way Of Writing rval*=2; 
	while(rval<Num) rval<<=1;
	return rval;
}



static void	setup_rw_source(jpeg_decompress_struct* cinfo, vfs::LoadedBlock* instream);

// A jpeglib source manager that reads from a LoadedBlock.  Paraphrased
// from IJG jpeglib jdatasrc.c.
static const int	IO_BUF_SIZE = 4096;
struct rw_source
{
	struct jpeg_source_mgr	m_pub;		/* public fields */
	vfs::LoadedBlock*	m_in_stream;
	size_t				m_Size_Read;	// This size that have read
	
	bool	m_start_of_file;		/* have we gotten any data yet? */
	JOCTET	m_buffer[IO_BUF_SIZE];	/* start of buffer */
	
	rw_source(vfs::LoadedBlock* in)
	:
	m_in_stream(in),
	m_start_of_file(true),
	m_Size_Read(0)
	// Constructor.  The caller is responsible for closing the input stream
	// after it's done using us.
	{
		// fill in function pointers...
		m_pub.init_source = init_source;
		m_pub.fill_input_buffer = fill_input_buffer;
		m_pub.skip_input_data = skip_input_data;
		m_pub.resync_to_restart = jpeg_resync_to_restart;	// use default method
		m_pub.term_source = term_source;
		m_pub.bytes_in_buffer = 0;
		m_pub.next_input_byte = NULL;
	}
	
	static void init_source(j_decompress_ptr cinfo)
	{
		rw_source*	src = (rw_source*) cinfo->src;
		src->m_start_of_file = true;
	}
	
	static boolean	fill_input_buffer(j_decompress_ptr cinfo)
	// Read data into our input buffer.  Client calls this
	// when it needs more data from the file.
	{
		rw_source*	src = (rw_source*) cinfo->src;
		
		size_t RemaingSize =(size_t) src->m_in_stream->Size - src->m_Size_Read;
		size_t SizeToRead  = 0; 
		if( RemaingSize >= IO_BUF_SIZE) 
			SizeToRead = IO_BUF_SIZE;
		else
			SizeToRead = RemaingSize;
		
		size_t	bytes_read = src->m_in_stream->ReadData(src->m_buffer, SizeToRead);
		
		
		if (!bytes_read) 
		{
			// Is the file completely empty?
			if (src->m_start_of_file) {
				// Treat this as a fatal error.
				throw "empty jpeg source stream.";
			}
			// warn("jpeg end-of-stream");
			
			// Insert a fake EOI marker.
			src->m_buffer[0] = (JOCTET) 0xFF;
			src->m_buffer[1] = (JOCTET) JPEG_EOI;
			bytes_read = 2;
		}
		
		src->m_Size_Read += SizeToRead;
		// Expose buffer state to clients.
		src->m_pub.next_input_byte = src->m_buffer;
		src->m_pub.bytes_in_buffer = SizeToRead;
		src->m_start_of_file = false;
		
		return TRUE;
	}
	
	static void	skip_input_data(j_decompress_ptr cinfo, long num_bytes)
	// Called by client when it wants to advance past some
	// uninteresting data.
	{
		rw_source*	src = (rw_source*) cinfo->src;
		
		// According to jpeg docs, large skips are
		// infrequent.  So let's just do it the simple
		// way.
		if (num_bytes > 0) 
		{
			while (num_bytes > (long) src->m_pub.bytes_in_buffer) {
				num_bytes -= (long) src->m_pub.bytes_in_buffer;
				fill_input_buffer(cinfo);
			}
			// Handle remainder.
			src->m_pub.next_input_byte += (size_t) num_bytes;
			src->m_pub.bytes_in_buffer -= (size_t) num_bytes;
		}
	}
	
	static void term_source(j_decompress_ptr cinfo)
	// Terminate the source.  Make sure we get deleted.
	{
		
	}
	
	
	void	discard_partial_buffer()
	{
		// Discard existing bytes in our buffer.
		m_pub.bytes_in_buffer = 0;
		m_pub.next_input_byte = NULL;
	}
};

static void	setup_rw_source(jpeg_decompress_struct* cinfo, vfs::LoadedBlock* instream)
// Set up the given decompress object to read from the given
// stream.
{
	// assert(cinfo->src == NULL);
	cinfo->src = (jpeg_source_mgr*) (new rw_source(instream));
}


